import axios, { formToJSON } from 'axios';

function Client(url, root) {
    console.log(url, root);

    var did = 0;
    if(url.startsWith("ws://")) {
        var wsc = {
            ws:null,
            cm:{},
        };
        var ws = Client.connMap[url];
        if(ws == null){
            ws = new WebSocket(url);
            ws.onopen = function() {
                console.log("websocket connected", ws.url);
                Client.connMap[url] = wsc;
            };
            ws.onclose = function() {
                console.log("websocket closed", ws.url);
                delete(Client.connMap, ws.url);
            }
            ws.onmessage = function(message) {
                var data = JSON.parse(message.data);
                var wsc = Client.connMap[ws.url];
                if(wsc != null && wsc.cm != null){
                    var callback = wsc.cm[data.id];
                    console.log(data);
                    callback && callback(data);
                }else{
                    console.log("websocket message", data);
                }
            };
            ws.onerror = function(e) {
                console.log("websocket error", e);
            };
        }
        wsc.ws = ws;
        did = 1; // 0 for subscribe
    }
    return {
        id:did,
        url: url,
        root:root,
        with: function(root) {
            return new Client(this.url, root);
        },
        post: function(data, callback) {
            data.id = this.id ++;
            if(this.root != null){
                if (data.params.length > 0) {
                    for(var i in data.params) {
                        data.params[i].root = this.root;
                    }
                }else{
                    data.params = [{root:this.root}]
                }
            }
            console.log(data.id, data.method, data.params);
            if(url.startsWith("ws://")) {
                var wsc = Client.connMap[this.url];
                wsc.cm[data.id]=callback;
                wsc.ws.send(JSON.stringify(data));
            }else{
                axios.post(this.url, data)
                .then(function (response) {
                    console.log(response.data);
                    callback && callback(response.data);
                }).catch(function (error) {
                    console.log(error);
                    callback && callback(error);
                });
            }
        },
        subscribe(callback) {
            if(this.url.startsWith("ws://")) {
                var wsc = Client.connMap[this.url];
                wsc.cm[0]=callback;
            }
        },
        block_number(callback) {
            this.blockNumber(callback);
        },
        blockNumber(callback){
            this.post({
                method:"blockNumber",
                params:[]
            }, callback);
        },
        get_account(address, callback) {
            this.getAccount({
                type: "AccountState", 
                address: address
            }, callback);
        },
        getAccount(p, callback){
            this.post({
                method:"getStateByAddress",
                params:[
                    p
                ]
            }, callback);
        },
        get_balance(address, callback) {
            this.getBalance({
                address
            }, callback);
        },
        getBalance(p, callback){
            this.post({
                method:"getBalance",
                params:[
                    p
                ]
            }, callback);
        },
        get_meta(symbol, callback) {
            this.getMeta({
                symbol
            }, callback);
        },
        getMeta(p, callback){
            this.post({
                method:"getMeta",
                params:[
                    p
                ]
            }, callback);
        },
        get_token(symbol, index, callback) {
            this.getMeta({
                symbol,
                index
            }, callback);
        },
        getToken(p, callback){
            this.post({
                method:"getToken",
                params:[
                    p
                ]
            }, callback);
        },
        get_block_by_number(num, callback){
            this.getBlockByNumber({
                num
            }, callback);
        },
        getBlockByNumber(p, callback){
            this.post({
                method:"getBlockByNumber",
                params:[
                    p
                ]
            }, callback);
        },
        get_transaction_by_hash(hash, callback){
            this.getTransactionByHash({
                hash
            }, callback);
        },
        getTransactionByHash(p, callback){
            this.post({
                method:"getTransactionByHash",
                params:[
                    p
                ]
            }, callback);
        },
        get_data(hash, format, callback) {
            this.getData({
                hash,
                format
            }, callback);
        },
        getData(p, callback){
            this.post({
                method:"getData",
                params:[
                    p
                ]
            }, callback);
        },
        get_user_data(address, account, callback){
            this.getUserData({
                address,
                account
            }, callback);
        },
        getUserData(p, callback){
            this.post({
                method:"getUserData",
                params:[
                    p
                ]
            }, callback);
        },
        get_contract_data(from, to, format, callback){
            this.getContractData({
                from,
                to,
                format
            }, callback);
        },
        getContractData(p, callback){
            this.post({
                method:"getContractData",
                params:[
                    p
                ]
            }, callback);
        },
        call_contract(data_address, code_address, method, params, callback){
            this.callContract({
                from: data_address, 
                to: code_address,
                method: method,
                params: params
            }, callback);
        },
        callContract(p, callback){
            this.post({
                method:"callContract",
                params:[
                    p
                ]
            }, callback);
        },
        start_service(nameOrAddress, input, callback){
            this.startService({
                service: nameOrAddress, 
                input:input,
            }, callback);
        },
        startService(p, callback){
            this.post({
                method:"startService",
                params:[
                    p
                ]
            }, callback);
        },
        join_service(nameOrAddress, input, callback){
            this.joinService({
                service: nameOrAddress, 
                input:input,
            }, callback);
        },
        joinService(p, callback){
            this.post({
                method:"joinService",
                params:[
                    p
                ]
            }, callback);
        },
        leave_service(nameOrAddress, input, callback){
            this.leaveService({
                service: nameOrAddress, 
                input:input,
            }, callback);
        },
        leaveService(p, callback){
            this.post({
                method:"leaveService",
                params:[
                    p
                ]
            }, callback);
        },
        list_service(nameOrAddress, callback){
            this.listService({
                service: nameOrAddress, 
            }, callback);
        },
        listService(p, callback){
            this.post({
                method:"listService",
                params:[
                    p
                ]
            }, callback);
        },
        call_service(service, input, callback){
            this.callService({
                service: service, 
                input:input,
            }, callback);
        },
        callService(p, callback){
            this.post({
                method:"callService",
                params:[
                    p
                ]
            }, callback);
        },
        message_number(service, callback) {
            this.messageNumber({
                service:service,
            },callback);
        },
        messageNumber(p, callback){
            this.post({
                method:"messageNumber",
                params:[
                    p
                ]
            }, callback);
        },
        get_message_by_index(service, index, callback){
            this.getMessageByIndex({
                service,
                index
            }, callback);
        },
        getMessageByIndex(p, callback){
            this.post({
                method:"getMessageByIndex",
                params:[
                    p
                ]
            }, callback);
        },
        get_transaction_count(address, callback) {
            this.getTransactionCount({
                address
            }, callback);
        },
        getTransactionCount(p, callback){
            this.post({
                method:"getTransactionCount",
                params:[
                    p
                ]
            }, callback);
        },
        send_raw_transaction(blob, callback) {
            this.sendRawTransaction({
                blob
            }, callback);
        },
        sendRawTransaction(p, callback) {
            this.post({
                method:"sendRawTransaction",
                params:[ 
                    p
                ]
            }, callback);
        },
        get_state_by_hash(hash, callback){
            this.getStateByHash({
                hash
            }, callback)
        },
        getStateByHash(p, callback) {
            this.post({
                method:"getStateByHash",
                params:[
                    p
                ]
            }, callback);
        },
        get_state_by_address(type, address, callback){
            this.getStateByAddress({
                type,
                address
            }, callback);
        },
        getStateByAddress(p, callback) {
            this.post({
                method:"getStateByAddress",
                params:[
                    p
                ]
            }, callback);
        },

        _sequence(i, results, funcs, params, callback) {
            if(i == funcs.length){
                callback({result:results});
            }else{
                var f = funcs[i];
                var p = params[i];
                var t = this;
                f.call(this, p, function(data){
                    results[i] = data;
                    t._sequence(i+1, results, funcs, params, callback);
                });
            }
        },
        sequence(funcs, params, callback) {
            if(funcs.length != params.length || funcs.length == 0) {
                callback({error:"error parameters"})
            }else{
                var results = new Array(funcs.length);
                this._sequence(0, results, funcs, params, callback);
            }
        },
        _parallel(i, count, items, callback){
            var item = items[i];
            item.f.call(this, item.p, function(data){
                items[item.i].r = data;
                items.count++;
                if(items.count == count) {
                    var results = new Array(count);
                    for(var i = 0; i < count; i ++) {
                        results[i] = items[i].r;
                    }
                    callback({result:results});
                }else{
                    console.log("parallel", count, i);
                }
            });
        },
        parallel(funcs, params, callback) {
            if(funcs.length != params.length || funcs.length == 0) {
                callback({error:"error parameters"})
            }else{
                var ll = funcs.length;
                var items = new Array(ll)
                items.count = 0;
                for(var i = 0; i < ll; i ++) {
                    var f = funcs[i];
                    var p = params[i];
                    var item = {i:i, f:f, p:p};
                    items[i] = item;
                    this._parallel(i, ll, items, callback);
                }
            }
        },
    };
}

Client.connMap = {};
export default Client;